Skip to content

Vitest: Reset playwright cursor position to avoid hover bug#34765

Merged
Sidnioulz merged 4 commits into
nextfrom
sidnioulz/reset-vitest-cursor-position
May 21, 2026
Merged

Vitest: Reset playwright cursor position to avoid hover bug#34765
Sidnioulz merged 4 commits into
nextfrom
sidnioulz/reset-vitest-cursor-position

Conversation

@Sidnioulz

@Sidnioulz Sidnioulz commented May 11, 2026

Copy link
Copy Markdown
Contributor

See internal bug report.

What I did

  • Added a browser command for Vitest Browser that moves the cursor to -1000, -1000
  • Called it in beforeEach to force the initial cursor position away from 0, 0

This should address a bug on Chromium in Linux where the :hover state gets recomputed without any actual mouse input events, and applied to elements at 0, 0. The fact that :hover gets applied causes axe-core results to be unreliable, as the default state of a component is no longer tested.

Checklist for Contributors

Testing

The changes in this PR are covered in the following automated tests:

I'm open to suggestions here.

Manual testing

  • Have a Linux user reproduce the issue on the original repro environment
    • git clone git@github.com:Sidnioulz/mealdrop.git
    • cd mealdrop && git checkout tree/telemetry-qa-3
    • yarn && yarn storybook
    • Run storybook tests with the a11y checkbox checked
    • Check the status of RestaurantCard Default, which should show a warning in the sidebar but not in the actual story
  • Now switch to canary for this PR
  • Repro as above and see if the warning is gone

Documentation

@jonniebigodes @yannbf do you think we need to document this in troubleshooting for the Vitest addon? Or should we add a JSDoc explanation for the browser command in the templates?

  • Add or update documentation reflecting your changes
  • If you are deprecating/removing a feature, make sure to update
    MIGRATION.MD

Checklist for Maintainers

  • When this PR is ready for testing, make sure to add ci:normal, ci:merged or ci:daily GH label to it to run a specific set of sandboxes. The particular set of sandboxes can be found in code/lib/cli-storybook/src/sandbox-templates.ts

  • Make sure this PR contains one of the labels below:

    Available labels
    • bug: Internal changes that fixes incorrect behavior.
    • maintenance: User-facing maintenance tasks.
    • dependencies: Upgrading (sometimes downgrading) dependencies.
    • build: Internal-facing build tooling & test updates. Will not show up in release changelog.
    • cleanup: Minor cleanup style change. Will not show up in release changelog.
    • documentation: Documentation only changes. Will not show up in release changelog.
    • feature request: Introducing a new feature.
    • BREAKING CHANGE: Changes that break compatibility in some way with current major version.
    • other: Changes that don't fit in the above categories.

🦋 Canary release

This pull request has been released as version 0.0.0-pr-34765-sha-ab58c528. Try it out in a new sandbox by running npx storybook@0.0.0-pr-34765-sha-ab58c528 sandbox or in an existing project with npx storybook@0.0.0-pr-34765-sha-ab58c528 upgrade.

More information
Published version 0.0.0-pr-34765-sha-ab58c528
Triggered by @Sidnioulz
Repository storybookjs/storybook
Branch sidnioulz/reset-vitest-cursor-position
Commit ab58c528
Datetime Mon May 11 14:50:56 UTC 2026 (1778511056)
Workflow run 25677621141

To request a new release of this pull request, mention the @storybookjs/core team.

core team members can create a new canary release here or locally with gh workflow run --repo storybookjs/storybook publish.yml --field pr=34765

Summary by CodeRabbit

  • Bug Fixes

    • Improved browser test stability by resetting mouse position before each test and injecting a handler to move the cursor off-screen, reducing hover-related race conditions.
  • Tests

    • Added unit tests covering the mouse-reset behavior, its no-op cases, fallback handling, and error propagation.

Review Change Stack

@Sidnioulz Sidnioulz requested a review from yannbf May 11, 2026 13:25
@Sidnioulz Sidnioulz self-assigned this May 11, 2026
Copilot AI review requested due to automatic review settings May 11, 2026 13:25
@Sidnioulz Sidnioulz added bug ci:normal Run our default set of CI jobs (choose this for most PRs). addon: vitest labels May 11, 2026
@Sidnioulz Sidnioulz requested a review from AriPerkkio May 11, 2026 13:26
@coderabbitai

coderabbitai Bot commented May 11, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a Vitest plugin command resetMousePosition (moves Playwright mouse off-screen) and an exported setup helper resetMousePositionBeforeTests that dynamically imports vitest/browser (with fallback) and invokes the command before each test; tests for the helper were added.

Changes

Mouse Position Reset Integration

Layer / File(s) Summary
Plugin Command Definition
code/addons/vitest/src/vitest-plugin/index.ts
Adds a type-only import from @vitest/browser-playwright and extends test.browser config with a commands.resetMousePosition that moves Playwright's mouse to (-1000, -1000).
Setup File Helper
code/addons/vitest/src/vitest-plugin/setup-file.ts
Exports resetMousePositionBeforeTests which dynamically imports vitest/browser commands, falls back to @vitest/browser/context, conditionally awaits commands.resetMousePosition, ignores known import/runtime errors, and preserves existing hooks.
Setup Lifecycle Wiring
code/addons/vitest/src/vitest-plugin/setup-file.ts
Retains globalProjectAnnotations.beforeAll() and wires the new helper via beforeEach(resetMousePositionBeforeTests).
Tests / Mocks
code/addons/vitest/src/vitest-plugin/setup-file.test.ts
Adds tests that mock vitest/browser and @vitest/browser/context to verify invocation, no-op for non-functions, fallback behavior, and error rethrowing; includes mock cleanup and un-mock steps.

Sequence Diagram(s)

sequenceDiagram
  participant TestRunner
  participant Setup as resetMousePositionBeforeTests
  participant BrowserCommands as vitest/browser.commands
  participant PlaywrightPage as Playwright.page
  TestRunner->>Setup: beforeEach()
  Setup->>BrowserCommands: import commands
  alt commands.resetMousePosition is function
    Setup->>BrowserCommands: await commands.resetMousePosition()
    BrowserCommands->>PlaywrightPage: page.mouse.move(-1000, -1000)
  else module missing
    Setup->>`@vitest/browser/context.commands`: import commands (fallback)
    alt fallback.commands.resetMousePosition is function
      Setup->>`@vitest/browser/context.commands`: await commands.resetMousePosition()
      `@vitest/browser/context.commands-`>>PlaywrightPage: page.mouse.move(-1000, -1000)
    end
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs


Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
code/addons/vitest/templates/vitest.config.template.ts (1)

12-17: ⚡ Quick win

Use a zero-argument BrowserCommand signature here.

Line 12 declares BrowserCommand<[number, number]>, but the command implementation takes only ctx and is called with no arguments (see commands.resetMousePosition() in the setup-file). The generic should be BrowserCommand<[]> to match the actual payload.

♻️ Proposed fix
-const resetMousePosition: BrowserCommand<[number, number]> = async (ctx) => {
+const resetMousePosition: BrowserCommand<[]> = async (ctx) => {
   if (ctx.provider.name !== 'playwright')
     throw new Error('resetMousePosition requires the Playwright provider');
   const frame = await ctx.frame();
   await frame.page().mouse.move(-1000, -1000);
 };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@code/addons/vitest/templates/vitest.config.template.ts` around lines 12 - 17,
The BrowserCommand generic for resetMousePosition is incorrect: change its type
from BrowserCommand<[number, number]> to BrowserCommand<[]> so the command
signature matches its zero-argument implementation (resetMousePosition(ctx) is
invoked with no payload). Update the declaration of resetMousePosition to use
BrowserCommand<[]> and ensure any references or exports that rely on its type
reflect the new generic.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@code/addons/vitest/templates/vitest.config.template.ts`:
- Around line 12-17: The BrowserCommand generic for resetMousePosition is
incorrect: change its type from BrowserCommand<[number, number]> to
BrowserCommand<[]> so the command signature matches its zero-argument
implementation (resetMousePosition(ctx) is invoked with no payload). Update the
declaration of resetMousePosition to use BrowserCommand<[]> and ensure any
references or exports that rely on its type reflect the new generic.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7b2d4a15-b49f-4dd5-9901-25328eb6b62a

📥 Commits

Reviewing files that changed from the base of the PR and between 0eab5b5 and aaef782.

📒 Files selected for processing (4)
  • code/addons/vitest/src/vitest-plugin/setup-file.ts
  • code/addons/vitest/templates/vitest.config.3.2.template.ts
  • code/addons/vitest/templates/vitest.config.4.template.ts
  • code/addons/vitest/templates/vitest.config.template.ts

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR aims to make Vitest Browser (Playwright) runs more reliable on Chromium/Linux by forcing the initial mouse cursor position away from (0,0), preventing unintended :hover recalculations that can skew axe-core a11y results.

Changes:

  • Added a resetMousePosition Playwright browser command to Vitest config templates.
  • Invoked commands.resetMousePosition() from the addon’s internal Vitest setup file in a beforeEach hook.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.

File Description
code/addons/vitest/templates/vitest.config.template.ts Adds a Playwright browser command and registers it in test.browser.commands.
code/addons/vitest/templates/vitest.config.4.template.ts Same as above for the Vitest 4 template variant.
code/addons/vitest/templates/vitest.config.3.2.template.ts Same as above for the Vitest 3.2 template variant.
code/addons/vitest/src/vitest-plugin/setup-file.ts Adds a beforeEach hook that conditionally calls the new browser command.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread code/addons/vitest/src/vitest-plugin/setup-file.ts
Comment thread code/addons/vitest/src/vitest-plugin/setup-file.ts Outdated
Comment thread code/addons/vitest/templates/vitest.config.template.ts Outdated
Comment thread code/addons/vitest/templates/vitest.config.4.template.ts Outdated
Comment thread code/addons/vitest/templates/vitest.config.3.2.template.ts Outdated
@Sidnioulz Sidnioulz force-pushed the sidnioulz/reset-vitest-cursor-position branch from 53fcb3e to ab58c52 Compare May 11, 2026 14:35

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
code/addons/vitest/src/vitest-plugin/setup-file.ts (1)

40-53: ⚡ Quick win

Remove unnecessary error handling and simplify the defensive check

Line 41's in operator is safe. The commands export is guaranteed to exist in both Vitest 3.x and 4.x, so the destructuring on Line 40 will always succeed. The catch block checking for "Cannot destructure property 'commands'" is dead code and can be removed. Simplify to safely check for the resetMousePosition method:

Proposed fix
 export const resetMousePositionBeforeTests = async () => {
   try {
     const { commands } = await import('vitest/browser');
-    if ('resetMousePosition' in commands && isFunction(commands.resetMousePosition)) {
+    if (isFunction(commands.resetMousePosition)) {
       await commands.resetMousePosition();
     }
   } catch (error) {
     // Ignore when vitest/browser is not found (Vitest 3)
     if (error instanceof Error && error.message.includes("Cannot find module 'vitest/browser'")) {
       return;
     }
-    // Ignore when commands is not exported by vitest/browser (Vitest 3)
-    if (
-      error instanceof Error &&
-      error.message.includes("Cannot destructure property 'commands'")
-    ) {
-      return;
-    }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@code/addons/vitest/src/vitest-plugin/setup-file.ts` around lines 40 - 53,
Remove the dead error check that looks for the "Cannot destructure property
'commands'" message in the catch block: keep the current dynamic import of
'vitest/browser' and the defensive runtime check for commands.resetMousePosition
(the in operator and isFunction(commands.resetMousePosition)), but delete the
entire conditional that tests error.message.includes("Cannot destructure
property 'commands'") so the catch only ignores missing-module errors and
otherwise rethrows or handles real errors; target the try/catch surrounding the
import and the symbols commands and resetMousePosition.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@code/addons/vitest/src/vitest-plugin/setup-file.ts`:
- Around line 40-53: Remove the dead error check that looks for the "Cannot
destructure property 'commands'" message in the catch block: keep the current
dynamic import of 'vitest/browser' and the defensive runtime check for
commands.resetMousePosition (the in operator and
isFunction(commands.resetMousePosition)), but delete the entire conditional that
tests error.message.includes("Cannot destructure property 'commands'") so the
catch only ignores missing-module errors and otherwise rethrows or handles real
errors; target the try/catch surrounding the import and the symbols commands and
resetMousePosition.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 12bbd42f-ce37-4159-ba2b-0b26502e97db

📥 Commits

Reviewing files that changed from the base of the PR and between 53fcb3e and ab58c52.

📒 Files selected for processing (2)
  • code/addons/vitest/src/vitest-plugin/setup-file.test.ts
  • code/addons/vitest/src/vitest-plugin/setup-file.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • code/addons/vitest/src/vitest-plugin/setup-file.test.ts

@AriPerkkio AriPerkkio left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good!

Test cases mock too much but that's out of scope of this PR. 🥹

Comment thread code/addons/vitest/src/vitest-plugin/setup-file.ts Outdated

Copilot AI commented May 12, 2026

Copy link
Copy Markdown
Contributor

Just as a heads up, I was blocked by some firewall rules while working on your feedback. Expand below for details.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • v3.vitest.dev
    • Triggering command: /home/REDACTED/work/_temp/ghcca-node/node/bin/node /home/REDACTED/work/_temp/ghcca-node/node/bin/node --enable-source-maps /home/REDACTED/work/_temp/copilot-developer-action-main/dist/index.js (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@code/addons/vitest/src/vitest-plugin/setup-file.test.ts`:
- Line 119: The test currently uses a broad await
expect(resetMousePositionBeforeTests()).rejects.toThrow(); update it to assert
the specific error or message thrown by resetMousePositionBeforeTests() to avoid
false positives — for example, change the assertion to await
expect(resetMousePositionBeforeTests()).rejects.toThrowError(<expected message
or regex>) or toThrowError(<ExpectedErrorClass>) and, if needed, adjust the test
stub/mocked dependency that causes the throw so the expected message/class is
known; target the resetMousePositionBeforeTests() call when making this change.
- Around line 82-138: Add a brief inline comment in the test suite above the
per-test vi.doMock() usages explaining that vi.doMock() is intentionally used
per-test as an exception to the spy-mocking rules because the implementation
under test uses dynamic await import() with error handling and needs per-case
module resolution/behavior (reference the vi.doMock calls and the
resetMousePositionBeforeTests tests); mention that hoisted vi.mock(spy: true)
cannot express these per-test variations, and suggest documenting or opening a
discussion in the spy-mocking guidelines if desired.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2861e223-dba4-4fd7-acea-894d018c1b54

📥 Commits

Reviewing files that changed from the base of the PR and between ab58c52 and 17754d5.

📒 Files selected for processing (2)
  • code/addons/vitest/src/vitest-plugin/setup-file.test.ts
  • code/addons/vitest/src/vitest-plugin/setup-file.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • code/addons/vitest/src/vitest-plugin/setup-file.ts

Comment thread code/addons/vitest/src/vitest-plugin/setup-file.test.ts
Comment thread code/addons/vitest/src/vitest-plugin/setup-file.test.ts
@storybook-app-bot

storybook-app-bot Bot commented May 20, 2026

Copy link
Copy Markdown

Package Benchmarks

Commit: 069a410, ran on 21 May 2026 at 07:27:50 UTC

No significant changes detected, all good. 👏

@Sidnioulz Sidnioulz force-pushed the sidnioulz/reset-vitest-cursor-position branch 2 times, most recently from 296d0fc to e1a8477 Compare May 21, 2026 06:39
@Sidnioulz Sidnioulz force-pushed the sidnioulz/reset-vitest-cursor-position branch from e1a8477 to 069a410 Compare May 21, 2026 07:14
@Sidnioulz Sidnioulz merged commit b433f35 into next May 21, 2026
140 checks passed
@Sidnioulz Sidnioulz deleted the sidnioulz/reset-vitest-cursor-position branch May 21, 2026 07:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

addon: vitest bug ci:normal Run our default set of CI jobs (choose this for most PRs).

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

4 participants